Voorkom dat uw app overspoeld wordt met WebSocket-berichten of dat de WebSocket-server overspoeld wordt met berichten door tegendruk toe te passen.
Achtergrond
De WebSocket API biedt een JavaScript-interface voor het WebSocket-protocol , waarmee een interactieve tweerichtingscommunicatiesessie tussen de browser van de gebruiker en een server kan worden geopend. Met deze API kunt u berichten naar een server sturen en gebeurtenisgestuurde reacties ontvangen zonder de server om een antwoord te hoeven vragen.
De Streams API
Met de Streams API kan JavaScript programmatisch toegang krijgen tot stromen van datablokken die via het netwerk worden ontvangen en deze naar wens verwerken. Een belangrijk concept in de context van streams is backpressure . Dit is het proces waarbij een enkele stream of een pijpketen de lees- of schrijfsnelheid regelt. Wanneer de stream zelf, of een stream verderop in de pijpketen, nog bezig is en nog niet klaar is om meer blokken te ontvangen, stuurt deze een signaal terug door de keten om de levering indien nodig te vertragen.
Het probleem met de huidige WebSocket API
Het is onmogelijk om tegendruk toe te passen op ontvangen berichten
Met de huidige WebSocket API wordt er gereageerd op een bericht via WebSocket.onmessage
, een EventHandler
die wordt aangeroepen wanneer een bericht van de server wordt ontvangen.
Stel dat je een applicatie hebt die zware dataverwerkingsbewerkingen moet uitvoeren wanneer er een nieuw bericht wordt ontvangen. Je zou de flow waarschijnlijk op dezelfde manier instellen als de onderstaande code, en aangezien je await
het resultaat van de process()
-aanroep, zou het goed moeten komen, toch?
// A heavy data crunching operation.
const process = async (data) => {
return new Promise((resolve) => {
window.setTimeout(() => {
console.log('WebSocket message processed:', data);
return resolve('done');
}, 1000);
});
};
webSocket.onmessage = async (event) => {
const data = event.data;
// Await the result of the processing step in the message handler.
await process(data);
};
Fout! Het probleem met de huidige WebSocket API is dat er geen manier is om tegendruk toe te passen. Wanneer berichten sneller aankomen dan de process()
-methode ze kan verwerken, zal het renderproces het geheugen vullen door die berichten te bufferen, niet meer reageren vanwege 100% CPU-gebruik, of beide.
Het uitoefenen van tegendruk op verzonden berichten is niet ergonomisch verantwoord
Het is mogelijk om tegendruk toe te passen op verzonden berichten, maar dit vereist het pollen van de eigenschap WebSocket.bufferedAmount
, wat inefficiënt en niet-ergonomisch is. Deze alleen-lezen eigenschap retourneert het aantal bytes aan gegevens dat in de wachtrij is geplaatst met behulp van aanroepen van WebSocket.send()
, maar nog niet naar het netwerk is verzonden. Deze waarde wordt teruggezet naar nul zodra alle gegevens in de wachtrij zijn verzonden, maar als u WebSocket.send()
blijft aanroepen, zal de waarde blijven stijgen.
Wat is de WebSocketStream API?
De WebSocketStream API pakt het probleem van niet-bestaande of niet-ergonomische tegendruk aan door streams te integreren met de WebSocket API. Dit betekent dat tegendruk "gratis" kan worden toegepast, zonder extra kosten.
Voorgestelde gebruiksscenario's voor de WebSocketStream API
Voorbeelden van sites die deze API kunnen gebruiken zijn:
- WebSocket-toepassingen met hoge bandbreedte die interactiviteit moeten behouden, met name video en schermdeling.
- Vergelijkbaar zijn video-opnames en andere applicaties die veel data in de browser genereren die naar de server geüpload moet worden. Met backpressure kan de client stoppen met het produceren van data in plaats van het opslaan van data in het geheugen.
Huidige status
Stap | Status |
---|---|
1. Maak een uitleg | Compleet |
2. Maak een eerste ontwerp van de specificatie | In uitvoering |
3. Verzamel feedback en herhaal het ontwerp | In uitvoering |
4. Oorsprongsproef | Compleet |
5. Lancering | Niet gestart |
Hoe de WebSocketStream API te gebruiken
De WebSocketStream API is gebaseerd op promises, waardoor het werken ermee in een moderne JavaScript-wereld vanzelfsprekend aanvoelt. Je begint met het bouwen van een nieuwe WebSocketStream
en geeft deze de URL van de WebSocket-server. Vervolgens wacht je tot de verbinding opened
is gebracht, wat resulteert in een ReadableStream
en/of een WritableStream
.
Door de methode ReadableStream.getReader()
aan te roepen, verkrijgt u uiteindelijk een ReadableStreamDefaultReader
, waaruit u vervolgens gegevens kunt read()
totdat de stream klaar is, dat wil zeggen totdat deze een object van de vorm {value: undefined, done: true}
retourneert.
Door de WritableStream.getWriter()
-methode aan te roepen, verkrijgt u uiteindelijk een WritableStreamDefaultWriter
, waarnaar u vervolgens write()
-gegevens kunt uitvoeren.
const wss = new WebSocketStream(WSS_URL);
const {readable, writable} = await wss.opened;
const reader = readable.getReader();
const writer = writable.getWriter();
while (true) {
const {value, done} = await reader.read();
if (done) {
break;
}
const result = await process(value);
await writer.write(result);
}
Tegendruk
Hoe zit het met de beloofde backpressure-functie? Die krijg je "gratis", zonder extra stappen. Als process()
extra tijd kost, wordt het volgende bericht pas verwerkt zodra de pijplijn klaar is. Op dezelfde manier gaat de stap WritableStreamDefaultWriter.write()
alleen verder als dit veilig is.
Geavanceerde voorbeelden
Het tweede argument van WebSocketStream is een optiepakket voor toekomstige uitbreidingen. De enige optie is protocols
, dat zich hetzelfde gedraagt als het tweede argument van de WebSocket-constructor :
const chatWSS = new WebSocketStream(CHAT_URL, {protocols: ['chat', 'chatv2']});
const {protocol} = await chatWSS.opened;
Het geselecteerde protocol
en de mogelijke extensions
maken deel uit van het woordenboek dat beschikbaar is via de WebSocketStream.opened
promise. Alle informatie over de actieve verbinding wordt door deze promise verstrekt, aangezien deze niet relevant is als de verbinding uitvalt.
const {readable, writable, protocol, extensions} = await chatWSS.opened;
Informatie over gesloten WebSocketStream-verbinding
De informatie die beschikbaar was via de gebeurtenissen WebSocket.onclose
en WebSocket.onerror
in de WebSocket API, is nu beschikbaar via de WebSocketStream.closed
promise. De promise wordt afgewezen in geval van een unclean close, anders wordt deze omgezet naar de code en reden die door de server zijn verzonden.
Alle mogelijke statuscodes en hun betekenis worden uitgelegd in de lijst met CloseEvent
-statuscodes .
const {code, reason} = await chatWSS.closed;
Een WebSocketStream-verbinding sluiten
Een WebSocketStream kan worden gesloten met een AbortController
. Geef daarom een AbortSignal
door aan de constructor WebSocketStream
.
const controller = new AbortController();
const wss = new WebSocketStream(URL, {signal: controller.signal});
setTimeout(() => controller.abort(), 1000);
Als alternatief kunt u ook de WebSocketStream.close()
methode gebruiken, maar het hoofddoel hiervan is het opgeven van de code en de reden waarom deze naar de server wordt verzonden.
wss.close({code: 4000, reason: 'Game over'});
Progressieve verbetering en interoperabiliteit
Chrome is momenteel de enige browser die de WebSocketStream API implementeert. Voor interoperabiliteit met de klassieke WebSocket API is het niet mogelijk om tegendruk toe te passen op ontvangen berichten. Het toepassen van tegendruk op verzonden berichten is wel mogelijk, maar vereist het pollen van de WebSocket.bufferedAmount
-eigenschap, wat inefficiënt en niet-ergonomisch is.
Functiedetectie
Om te controleren of de WebSocketStream API wordt ondersteund, gebruikt u:
if ('WebSocketStream' in window) {
// `WebSocketStream` is supported!
}
Demonstratie
In de ondersteunde browsers kunt u de WebSocketStream API in actie zien in het ingesloten iframe of rechtstreeks op Glitch .
Feedback
Het Chrome-team wil graag uw ervaringen met de WebSocketStream API horen.
Vertel ons over het API-ontwerp
Werkt er iets aan de API dat niet werkt zoals je had verwacht? Of ontbreken er methoden of eigenschappen die je nodig hebt om je idee te implementeren? Heb je een vraag of opmerking over het beveiligingsmodel? Dien een spec-issue in op de betreffende GitHub-repository of voeg je mening toe aan een bestaand issue.
Meld een probleem met de implementatie
Heb je een bug gevonden in de implementatie van Chrome? Of wijkt de implementatie af van de specificatie? Meld een bug op new.crbug.com . Zorg ervoor dat je zoveel mogelijk details en eenvoudige instructies voor reproductie geeft en voer Blink>Network>WebSockets
in het vak Componenten in. Glitch is ideaal voor het delen van snelle en eenvoudige reproductiegevallen.
Toon ondersteuning voor de API
Bent u van plan de WebSocketStream API te gebruiken? Uw publieke steun helpt het Chrome-team om functies te prioriteren en laat andere browserleveranciers zien hoe belangrijk het is om deze te ondersteunen.
Stuur een tweet naar @ChromiumDev met de hashtag #WebSocketStream
en laat ons weten waar en hoe je deze gebruikt.
Nuttige links
- Publieke uitleg
- WebSocketStream API-demo | Bron van de WebSocketStream API-demo
- Tracking-bug
- ChromeStatus.com-vermelding
- Knippercomponent:
Blink>Network>WebSockets
Dankbetuigingen
De WebSocketStream API is geïmplementeerd door Adam Rice en Yutaka Hirano .